package top.infopub.util.convert;


import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.NotReadablePropertyException;
import org.springframework.util.ReflectionUtils;

import top.infopub.exception.ConvertException;
import top.infopub.util.convert.impl.DefaultConvertor;


/**
 * Bean属性转换
 * @author Awoke
 * @version 2018年4月27日
 * @see ConvertBeanUtil
 * @since
 */
public final class ConvertBeanUtil {

    /**
     * 将源bean属性转换为targetClazz对应的bean属性
     * @param targetBeanClazz 使用了BeanPropertyAnno注解目标要转化成的bean.class
     * @param includeSuperFields 转换设置属性时，是否包含目标bean的父类字段，如果目标bean没有父类，设置为false
     * @param sourceBeanWrapper 源bean ，可使用new BeanWrapperImpl(sourcBean)生成
     * @return 
     * @see
     */
    public static <T> T copy(Class<T> targetBeanClazz, boolean includeSuperFields,
                             BeanWrapper sourceBeanWrapper) {
        T result = newInstance(targetBeanClazz);
        // 目标clazz字段 
        Field[] fields = targetBeanClazz.getDeclaredFields();
        if (includeSuperFields) {
            // 获得父类的字段 用于不反射父类的属性 
            Class<?> superClass = targetBeanClazz.getSuperclass();
            Field[] superfields = superClass.getDeclaredFields();
            fields = ArrayUtils.addAll(fields, superfields);
        }
        for (Field field : fields) {
            // 目标bean字段未加注解则不进行反射set
            if (!field.isAnnotationPresent(ConvertBeanAnno.class)) {
                continue;
            }
            ConvertBeanAnno anno = field.getAnnotation(ConvertBeanAnno.class);
            // 获得对应的源bean字段名
            String propertyName = anno.sourceProp();
            // 未设置则使用目标bean的字段名字
            if (StringUtils.isBlank(propertyName)) {
                propertyName = field.getName();
            }
            // 获得对应的转换器
            Class<? extends Convertor> convert = anno.convertor();
            // 获得源字段的值
            Object value = null;
            try {
                value = sourceBeanWrapper.getPropertyValue(propertyName);
            }
            catch (NotReadablePropertyException e) {
                continue;
            }
            if (value == null) {
                continue;
            }
            Object realValue = null;
            // 采用的是默认的转换器，即用spring提供DefaultConversionService 进行转换字段
            if (convert.equals(DefaultConvertor.class)) {
                // 属性类型不相同 则进行转换
                if (!sourceBeanWrapper.getPropertyType(propertyName).equals(field.getType())) {
                    // 处理 java.lang.String to type java.math.BigDecimal for value ' ' 异常
                    if (value != null && StringUtils.isBlank(value.toString())
                        && BigDecimal.class.equals(field.getType())) {
                        realValue = null;
                    }
                    else {
                        realValue = Convertor.CONVERT_SERVICES.convert(value, field.getType());
                    }
                }
                // 属性类型相同 则不进行转换
                else {
                    realValue = value;
                }
                doSetterMethod(result, field.getName(), field.getType(), realValue);
            }
            // 采用BeanPropertyAnno声明的Convert进行转换
            else {
                realValue = newInstance(convert).convert(value, field.getType());
            }
            doSetterMethod(result, field.getName(), field.getType(), realValue);
        }
        return result;

    }

    /**
     * newInstance
     * @param clazz
     * @return 
     * @see
     */
    private static <T> T newInstance(Class<T> clazz) {
        try {
            return clazz.newInstance();
        }
        catch (Exception e) {
            throw new ConvertException("new instance exception", e);
        }
    }

    /**
     * set字段属性
     * @param result
     * @param field
     * @param type
     * @param params 
     * @see
     */
    private static void doSetterMethod(Object result, String field, Class<?> type,
                                       Object... params) {
        try {
            String setterMethodName = "set" + field.substring(0, 1).toUpperCase()
                                      + field.substring(1);
            Method setterMethod = ReflectionUtils.findMethod(result.getClass(), setterMethodName,
                type);
            setterMethod.invoke(result, params);
        }
        catch (Exception e) {
            throw new ConvertException("do setter exception ", e);
        }
    }

}
